/***************************************************************************
 *   Copyright (c) 2019 Abdullah Tahiri <abdullah.tahiri.yo@gmail.com>     *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#ifndef SKETCHER_SOLVERGEOMETRYEXTENSION_H
#define SKETCHER_SOLVERGEOMETRYEXTENSION_H

#include <Base/Exception.h>
#include <Mod/Part/App/GeometryExtension.h>
#include <Mod/Sketcher/SketcherGlobal.h>

#include "GeoEnum.h"


namespace Sketcher
{

class SketcherExport SolverGeometryExtension: public Part::GeometryExtension
{
    TYPESYSTEM_HEADER_WITH_OVERRIDE();

public:
    enum SolverStatus
    {
        FullyConstraint = 0,
        NotFullyConstraint = 1,
        NumSolverStatus
    };

    enum ParameterStatus
    {
        Dependent = 0,
        Independent = 1,
        NumParameterStatus
    };

    class PointParameterStatus
    {
    public:
        explicit PointParameterStatus(ParameterStatus status)
        {
            setStatus(status);
        }
        PointParameterStatus(ParameterStatus statusx, ParameterStatus statusy)
        {
            setStatus(statusx, statusy);
        }

        PointParameterStatus(const PointParameterStatus&) = default;
        PointParameterStatus& operator=(const PointParameterStatus&) = default;
        PointParameterStatus(PointParameterStatus&&) = default;
        PointParameterStatus& operator=(PointParameterStatus&&) = default;

        ParameterStatus getStatus() const
        {
            return (xstatus == Independent && ystatus == Independent) ? Independent : Dependent;
        }
        ParameterStatus getStatusx() const
        {
            return xstatus;
        }
        ParameterStatus getStatusy() const
        {
            return ystatus;
        }

        bool isXDoF()
        {
            return xstatus == Dependent;
        }
        bool isYDoF()
        {
            return ystatus == Dependent;
        }

        int getDoFs()
        {
            bool xfree = isXDoF();
            bool yfree = isYDoF();

            if (xfree && yfree) {
                return 2;
            }
            else if (xfree || yfree) {
                return 1;
            }
            else {
                return 0;
            }
        }

        void setStatus(ParameterStatus status)
        {
            xstatus = status;
            ystatus = status;
        }
        void setStatus(ParameterStatus statusx, ParameterStatus statusy)
        {
            xstatus = statusx;
            ystatus = statusy;
        }
        void setStatusx(ParameterStatus statusx)
        {
            xstatus = statusx;
        }
        void setStatusy(ParameterStatus statusy)
        {
            ystatus = statusy;
        }

    private:
        ParameterStatus xstatus;
        ParameterStatus ystatus;
    };

    class EdgeParameterStatus
    {
    public:
        EdgeParameterStatus() = default;

        void init(int nparams)
        {
            pstatus.resize(nparams, ParameterStatus::Dependent);
        }

        ParameterStatus getStatus() const
        {
            return std::all_of(pstatus.begin(),
                               pstatus.end(),
                               [](const auto& v) {
                                   return v == Independent;
                               })
                ? Independent
                : Dependent;
        }

        void setStatus(ParameterStatus status)
        {
            std::fill(pstatus.begin(), pstatus.end(), status);
        }

        void setStatus(int index, ParameterStatus status)
        {
            if (index >= int(pstatus.size())) {
                pstatus.resize(index + 1, ParameterStatus::Dependent);
            }

            pstatus.at(index) = status;
        };

    protected:
        std::vector<ParameterStatus> pstatus;
    };

    class Point: public EdgeParameterStatus
    {
    public:
        Point() = default;
    };

    class Line: public EdgeParameterStatus
    {
    public:
        Line() = default;
    };

    class Arc: public EdgeParameterStatus
    {
    public:
        Arc() = default;

        ParameterStatus getRadiusStatus() const
        {
            return pstatus[0];
        }
        bool isRadiusDoF() const
        {
            return pstatus[0] == Dependent;
        }
        ParameterStatus getStartParameter() const
        {
            return pstatus[1];
        }
        ParameterStatus getEndParameter() const
        {
            return pstatus[2];
        }
    };

    class Circle: public EdgeParameterStatus
    {
    public:
        Circle() = default;

        ParameterStatus getRadiusStatus() const
        {
            return pstatus[0];
        }
        bool isRadiusDoF() const
        {
            return pstatus[0] == Dependent;
        }
    };

    class ArcOfEllipse: public EdgeParameterStatus
    {
    public:
        ArcOfEllipse() = default;

        ParameterStatus getFocusXStatus() const
        {
            return pstatus[0];
        }
        ParameterStatus getFocusYStatus() const
        {
            return pstatus[1];
        }
        ParameterStatus getFocusMinorRadiusStatus() const
        {
            return pstatus[2];
        }
        ParameterStatus getStartParameter() const
        {
            return pstatus[3];
        }
        ParameterStatus getEndParameter() const
        {
            return pstatus[4];
        }

        bool isFocusDoF() const
        {
            return pstatus[0] == Dependent || pstatus[1] == Dependent;
        }
        bool isMinorRadiusDoF() const
        {
            return (pstatus[2] == Dependent);
        }
    };

    class Ellipse: public EdgeParameterStatus
    {
    public:
        Ellipse() = default;

        ParameterStatus getFocusXStatus() const
        {
            return pstatus[0];
        }
        ParameterStatus getFocusYStatus() const
        {
            return pstatus[1];
        }
        ParameterStatus getFocusMinorRadiusStatus() const
        {
            return pstatus[2];
        }

        bool isFocusDoF() const
        {
            return pstatus[0] == Dependent || pstatus[1] == Dependent;
        }
        bool isMinorRadiusDoF() const
        {
            return (pstatus[2] == Dependent);
        }
    };

    class ArcOfHyperbola: public EdgeParameterStatus
    {
    public:
        ArcOfHyperbola() = default;

        ParameterStatus getFocusXStatus() const
        {
            return pstatus[0];
        }
        ParameterStatus getFocusYStatus() const
        {
            return pstatus[1];
        }
        ParameterStatus getFocusMinorRadiusStatus() const
        {
            return pstatus[2];
        }
        ParameterStatus getStartParameter() const
        {
            return pstatus[3];
        }
        ParameterStatus getEndParameter() const
        {
            return pstatus[4];
        }
    };

    class ArcOfParabola: public EdgeParameterStatus
    {
    public:
        ArcOfParabola() = default;

        ParameterStatus getFocusXStatus() const
        {
            return pstatus[0];
        }
        ParameterStatus getFocusYStatus() const
        {
            return pstatus[1];
        }
    };

    class BSpline: public EdgeParameterStatus
    {
    public:
        BSpline() = default;

        ParameterStatus getPoleXStatus(int poleindex) const
        {
            int npoles = pstatus.size() / 3;

            if (poleindex < npoles) {
                return pstatus[poleindex * 2];
            }

            THROWM(Base::IndexError, "Pole index out of range")
        }

        ParameterStatus getPoleYStatus(int poleindex) const
        {
            int npoles = pstatus.size() / 3;

            if (poleindex < npoles) {
                return pstatus[poleindex * 2 + 1];
            }

            THROWM(Base::IndexError, "Pole index out of range")
        }

        ParameterStatus getWeightStatus(int weightindex) const
        {
            int nweights = pstatus.size() / 3;

            if (weightindex < nweights) {
                return pstatus[nweights * 2 + weightindex];
            }

            THROWM(Base::IndexError, "Weight index out of range")
        }
    };


    SolverGeometryExtension();
    ~SolverGeometryExtension() override = default;

    std::unique_ptr<Part::GeometryExtension> copy() const override;

    PyObject* getPyObject() override;

    void notifyAttachment(Part::Geometry* geo) override;

    SolverStatus getGeometry() const
    {
        return (Edge.getStatus() == Independent && Start.getStatus() == Independent
                && End.getStatus() == Independent && Mid.getStatus() == Independent)
            ? FullyConstraint
            : NotFullyConstraint;
    }

    ParameterStatus getEdge() const
    {
        return Edge.getStatus();
    }
    Point& getPoint();
    Line& getLine();
    Arc& getArc();
    Circle& getCircle();
    ArcOfEllipse& getArcOfEllipse();
    Ellipse& getEllipse();
    ArcOfHyperbola& getArcOfHyperbola();
    ArcOfParabola& getArcOfParabola();
    BSpline& getBSpline();
    EdgeParameterStatus getEdgeParameters()
    {
        return Edge;
    }
    void setEdge(ParameterStatus status)
    {
        Edge.setStatus(status);
    }
    void setEdge(int paramindex, ParameterStatus status)
    {
        Edge.setStatus(paramindex, status);
    }

    ParameterStatus getStart() const
    {
        return Start.getStatus();
    }
    PointParameterStatus getStartPoint() const
    {
        return Start;
    }
    void setStart(ParameterStatus xstatus, ParameterStatus ystatus)
    {
        Start.setStatus(xstatus, ystatus);
    }
    void setStartx(ParameterStatus xstatus)
    {
        Start.setStatusx(xstatus);
    }
    void setStarty(ParameterStatus ystatus)
    {
        Start.setStatusy(ystatus);
    }

    ParameterStatus getMid() const
    {
        return Mid.getStatus();
    }
    PointParameterStatus getMidPoint() const
    {
        return Mid;
    }
    void setMid(ParameterStatus xstatus, ParameterStatus ystatus)
    {
        Mid.setStatus(xstatus, ystatus);
    }
    void setMidx(ParameterStatus xstatus)
    {
        Mid.setStatusx(xstatus);
    }
    void setMidy(ParameterStatus ystatus)
    {
        Mid.setStatusy(ystatus);
    }

    ParameterStatus getEnd() const
    {
        return End.getStatus();
    }
    PointParameterStatus getEndPoint() const
    {
        return End;
    }

    void setEnd(ParameterStatus xstatus, ParameterStatus ystatus)
    {
        End.setStatus(xstatus, ystatus);
    }
    void setEndx(ParameterStatus xstatus)
    {
        End.setStatusx(xstatus);
    }
    void setEndy(ParameterStatus ystatus)
    {
        End.setStatusy(ystatus);
    }

    PointParameterStatus getPoint(Sketcher::PointPos pos) const;

    void init(ParameterStatus status)
    {
        Edge.setStatus(status);
        Start.setStatus(status);
        Mid.setStatus(status);
        End.setStatus(status);
    }

protected:
    void copyAttributes(Part::GeometryExtension* cpy) const override;

private:
    SolverGeometryExtension(const SolverGeometryExtension&) = default;

    void ensureType(const Base::Type& type);

private:
    EdgeParameterStatus Edge;

    PointParameterStatus Start;
    PointParameterStatus Mid;
    PointParameterStatus End;

    Base::Type GeometryType;
};

}  // namespace Sketcher


#endif  // SKETCHER_SOLVERGEOMETRYEXTENSION_H
